package org.swift.commons.xml;

import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.xpath.XPathException;

import org.swift.commons.xml.vtd.AutomaticPilot;
import org.swift.commons.xml.vtd.FileNavigator;
import org.swift.commons.xml.vtd.MemoryNavigator;
import org.swift.commons.xml.vtd.NavigationException;
import org.swift.commons.xml.vtd.Navigator;
import org.swift.commons.xml.vtd.XPathEvaluationException;

public class Matcher implements Iterator<Match>{	
	private final Navigator nav;
	private final AutomaticPilot autoPilot;	
	private int index;
	private final Map<String, String> namespaces = new HashMap<String, String>();
	
	private String singleValue = null;
	
	public Matcher(String xpath, byte[] xml, Map<String, String> namespaces) throws XmlParseException, XPathException {		
		this(new MemoryNavigator(xml), xpath, namespaces);
	}
	
	public Matcher(String xpath, File file, Map<String, String> namespaces) throws XPathException {
		this(new FileNavigator(file), xpath, namespaces);		
	}
	
	public Matcher(Navigator navigator, String xpath,
			Map<String, String> namespaces) throws XPathException {
		try {			
			this.namespaces.putAll(namespaces);
			this.nav = navigator;
			this.autoPilot = navigator.getAutomaticPilot();
			addNamespaces(this.autoPilot, namespaces);
		
			this.autoPilot.selectXPath(xpath);
			if (xpath.startsWith("/")) { //normal xpath						
				index = autoPilot.evalXPath();
			} else {  //assuming it's a single value xpath like count() or substring()			
				this.singleValue = this.autoPilot.evalXPathToString();
				index = 0;
			}
		} catch (Exception e) {
			throw new XPathException(e);
		}
	}

	/**
	 * Called internally to add the list of namespaces to the autopilot
	 * 
	 * @param autoPilot
	 * @param namespaces
	 */
	protected void addNamespaces(AutomaticPilot autoPilot, Map<String, String> namespaces) {
		for(Entry<String, String> entry : namespaces.entrySet()) {
			autoPilot.declareXPathNameSpace(entry.getKey(), entry.getValue());
		}		
	}

	public boolean hasNext() {
		return index != -1;		
	}

	public Match next() {		
		if (this.singleValue != null) {
			this.index = -1;
			return new ValueMatch(this.singleValue, this.namespaces);
		}
		
		try {			
			Match match = nav.matchAt(this.index, this.namespaces);			
			this.index = autoPilot.evalXPath();
			return match;
		} catch (NavigationException e) {
			throw new RuntimeException(e);
		} catch (XPathEvaluationException e) {
			throw new RuntimeException(e);
		}
	}
	
	protected int currentType() {
		return nav.getTokenType(index);
	}
	
	public void remove() {
		throw new RuntimeException("Remove is not supported");
		
	}
}
